package com.cloud.prac.endecrypt;

import cn.hutool.core.util.RandomUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.Builder;
import lombok.Data;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.lang3.StringUtils;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.*;

public class utils {

    private static final String rsaPublicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDH5RuutBp5uShEwjg2vg3YWDFo4tgbzzN+U08VFzm8X14C1wM0nKt8HulXEWpLKYNPKEfw62RnMUUgHXcOKwocOIcAb+cREtMNymaS9o6LPo+u9hs5Qe045mD0b9jkEVAjKvaubDzbx2eT/6Z7WuYOL49lk7TDnJSsWcEuwdgAyQIDAQAB";
    private static final String rsaPrivateKey = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAMflG660Gnm5KETCODa+DdhYMWji2BvPM35TTxUXObxfXgLXAzScq3we6VcRakspg08oR/DrZGcxRSAddw4rChw4hwBv5xES0w3KZpL2jos+j672GzlB7TjmYPRv2OQRUCMq9q5sPNvHZ5P/pnta5g4vj2WTtMOclKxZwS7B2ADJAgMBAAECgYAwFofAUYeE/OwZDngjgzklcKICT4AZwJDstPHzavDyxiaBnGQjBgWjCHSuA3yEtGnoYxJbfOVchdfRUAVSHTyC/Ka9EUVVu0xlV7kKZwdq8UqCR+wbvfqgC6w4Je+u+dRYi3/ETo7ZNlOSRlBp9ccmlR2XAR65gsBXOXrceIfJuQJBAOM24YYK4x8t/hgAbwIznPCiGDitFkNkvDiPtq7NJkkMKXPU3/PPx99o4a13FxvlEGh8svLKsAv8Qkv/+ZU3dg8CQQDhODD4LMxrUBiwxN4qK0pkT+4O3v4Z76CGxzWZsF/fZhFsscVnldZpfgVk0Ice/33awAO9TUby3fGKyCh8izOnAkEA2y9VDU6xXKGsjZDVhYUurz9fKEVoxaiGnfWdIDFM7oip8FB5niRhxpXRptnMVB973Z/1rJt0iotVlSUgqh5vZwJANqzThowpnCu3ssI4RPh5eNzGqCmOenFoARA/fO4KeEtpE4Acskb35GhJmu3cv7O6s+5FeiFWhgNHbi+lVIbepQJABOisvNOnajjSsjwPDniXwyhHO6GWDPKV5DvLOlbRR5qmko27h18RE7UocMJmcQsLfAboFPOe1B5Zp84Itim1TQ==";
    private static final String partnerPublicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCw1UlHVTISqRVHTag5CbgdL7Icut17R81RKU6C1VciIqBeWVx8EC1kf682Hs5nFVYN7QCSo/10tuvT3dzW5pDzwwRsbAIBTbiZ2AzwTqEyRuvBatTpmHK95qj+8n8IsLsX9IicfOqmZ4WMTUCE2pYBvEhqlGDDwd2Y4B+zaIOguQIDAQAB";
    private static final String partnerPrivateKey = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALDVSUdVMhKpFUdNqDkJuB0vshy63XtHzVEpToLVVyIioF5ZXHwQLWR/rzYezmcVVg3tAJKj/XS269Pd3NbmkPPDBGxsAgFNuJnYDPBOoTJG68Fq1OmYcr3mqP7yfwiwuxf0iJx86qZnhYxNQITalgG8SGqUYMPB3ZjgH7Nog6C5AgMBAAECgYAeMWy/p0ZTdVdSw2mzw3sSULCCw01J3WqrLvhe0np8M5laQrvN2x1FCaArB3uKklQNMKhmYWXHZexCUqGAeAHLtAG13owKwCuXRaVy825ICJrJDfIKq9d8Ea2RRCje7Egn2f7ow3pM+pGEWol76+Gh8cBwmfAN7NwEeWbNQASrAQJBAPHR/qS1mQdvZC4FPdNvsi/BEPd2/MvD+E+EgVZIXiRp7BF50OLZ2AiicwkIYyMxDoGtm0IFpKTAzKNBTt7dP+kCQQC7M8Iw24ujb4T5rFs0v32lLAYiIF7HO/lnhQ7jlqK+TYIKWBPi1nU4Qigiuu9mdrUEGgBFZGm4LY82tVXglihRAkAam3uETgLvgX8pXSRHXnPhs0GVqP75wK9SuIauornREeKKrbehLk0J7i3wIX8fASfSdvKtWgeapSgfEnGGLhv5AkAe93PjEx4pPfRYPLuloRuOrBajpIkpGfGqvyb0XOG06ytcvK8lceZYFRGAlT2uzGorMIXk9LRMRDURjrOTACPRAkEAmvBVO7NNUHbcu5T+vqWibBHdjC89eX7UvUhE15u9z5F+y9xMicZdIqxrg75RWDMiUEmwwxYP8CIJL9uQSCyYLg==";

    public static void main(String[] args) throws Exception {
        //业务参数 bizContent
        String bizContent = "{\n" +
                "    \"name\":\"我是转转\",\n" +
                "    \"contractType\": \"AUTH\",\n" +
                "    \"zzOpenId\": \"105ee4b8c085dc2e02259d62376b202de27e95ee5f08f8638ea3fdec2833dc0f\"\n" +
                "}";

        //模拟转转对请求参数加密
        //1 合作伙伴公钥：对AES key加密，得到randomKey（随请求带出）
        //2 转转私钥：对参数签名，得到sign（随请求带出）
        String str = encrypted(bizContent);
        System.out.println("加密后：" + str);

        //模拟资方解密转转的请求参数
        //3 转转公钥：校验签名（sign）
        //4 合作伙伴私钥：解密randomKey，得到AES key
        //5 使用AES key，解密业务参数
        String t = decrypted(str);
        System.out.println("解密后：" + t);

    }

    /**
     * 对请求参数解密
     *
     * @param postStr
     * @return
     * @throws Exception
     */
    static String decrypted(String postStr) throws Exception {
        //资方接收到转转的参数
        Map<String, Object> receivedParamMap = JSONObject.parseObject(postStr).getInnerMap();
        String sign = (String) receivedParamMap.get("sign");
        receivedParamMap.remove("sign");
        Map<String, Object> receivedParaTreeMap = new TreeMap(receivedParamMap);       // salt值 默认：ps&d$
        String sortParamStr = sortParams(receivedParaTreeMap, "ps&d$");

        //签名校验，根据结果决定流程
        boolean f = rsaCheckContent(sortParamStr, sign, rsaPublicKey);

        //获取DES decryptKey（桔子RSA私钥解密）
        String aesDecryptKey = rsaDecryptByPrivate(partnerPrivateKey, (String) receivedParamMap.get("randomKey"));

        return aesDecrypt(aesDecryptKey, (String) receivedParamMap.get("bizContent"));
    }


    /**
     * 对请求参数加密
     *
     * @return
     * @throws Exception
     */
    static String encrypted(String bizContent) throws Exception {
        RequestProtocol.RequestProtocolBuilder builder = RequestProtocol.builder().appId("zz-api").timestamp(System.currentTimeMillis());
        //AES secretKey（随机生成）
        String aesEncryptKey = RandomUtil.randomString(16);
        //AES算法加密业务参数
        String encryptedBizContent = aesEncrypt(aesEncryptKey, bizContent);
        builder.bizContent(encryptedBizContent);

        //使用合作伙伴RSA公钥加密AES secretKey
        String randomKey = rsaEncryptByPublic(partnerPublicKey, aesEncryptKey, StandardCharsets.UTF_8.name());
        builder.randomKey(randomKey);

        Map<String, Object> requestMap = JSONObject.parseObject(JSON.toJSONString(builder.build())).getInnerMap();

        //使用转转RSA私钥对参数签名（salt值 双方约定，默认：ps&d$）
        String sign = rsaSign(sortParams(new TreeMap(requestMap), "ps&d$"), rsaPrivateKey);
        requestMap.put("sign", sign);

        //模拟转转http发送的请求包
        return JSON.toJSONString(requestMap);
    }

    /**
     * RSA 私钥解密
     *
     * @param encodedKey
     * @param content
     * @return
     * @throws Exception
     */
    public static String rsaDecryptByPrivate(String encodedKey, String content) throws Exception {
        if (StringUtils.isEmpty(encodedKey)) {
            throw new Exception("解密密钥为空");
        }
        if (StringUtils.isEmpty(content)) {
            throw new Exception("待解密数据为空");
        }
        PrivateKey key = null;
        try {
            key = getPrivateKeyFromPKCS8("RSA", encodedKey);
        } catch (Exception var5) {
            throw new Exception("解密异常", var5);
        }
        return decrypt(key, content, "utf-8");
    }


    /**
     * RSA 解密
     *
     * @param encodedKey
     * @param content
     * @param charset
     * @return
     * @throws Exception
     */
    public static String decrypt(Key encodedKey, String content, String charset) throws Exception {
        try {
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            Throwable var4 = null;
            String var11;
            try {
                Cipher cipher = Cipher.getInstance("RSA");
                cipher.init(2, encodedKey);
                byte[] data = Base64.getDecoder().decode(content);
                int inputLen = data.length;
                int offSet = 0;

                for (int i = 0; inputLen - offSet > 0; offSet = i * 128) {
                    byte[] cache;
                    if (inputLen - offSet > 128) {
                        cache = cipher.doFinal(data, offSet, 128);
                    } else {
                        cache = cipher.doFinal(data, offSet, inputLen - offSet);
                    }
                    ++i;
                    out.write(cache, 0, cache.length);
                }
                var11 = StringUtils.isBlank(charset) ? new String(out.toByteArray()) : new String(out.toByteArray(), charset);
            } catch (Throwable var21) {
                var4 = var21;
                throw var21;
            } finally {
                if (var4 != null) {
                    try {
                        out.close();
                    } catch (Throwable var20) {
                        var4.addSuppressed(var20);
                    }
                } else {
                    out.close();
                }
            }
            return var11;
        } catch (Exception var23) {
            throw new Exception("解密处理异常", var23);
        }
    }


    /**
     * RSA签名 privateKey
     *
     * @param content
     * @param privateKey
     * @return
     * @throws Exception
     */
    public static String rsaSign(String content, String privateKey) throws Exception {
        try {
            PrivateKey priKey = getPrivateKeyFromPKCS8("RSA", privateKey);
            Signature signature = Signature.getInstance("SHA256WithRSA");
            signature.initSign(priKey);
            signature.update(content.getBytes(StandardCharsets.UTF_8));
            return Base64.getEncoder().encodeToString(signature.sign());
        } catch (Exception var5) {
            throw new Exception("RSA签名异常", var5);
        }
    }

    /**
     * RSA签名 publicKey
     *
     * @param content
     * @param sign
     * @param publicKey
     * @return
     * @throws Exception
     */
    public static boolean rsaCheckContent(String content, String sign, String publicKey) throws Exception {
        try {
            PublicKey pubKey = getPublicKeyFromX509("RSA", publicKey);
            Signature signature = Signature.getInstance("SHA256WithRSA");
            signature.initVerify(pubKey);
            signature.update(content.getBytes(StandardCharsets.UTF_8.name()));
            return signature.verify(Base64.getDecoder().decode(sign.getBytes()));
        } catch (Exception var6) {
            throw new Exception("RSA签名验证错误,响应签名:" + sign, var6);
        }
    }


    /**
     * RSA 公钥加密
     *
     * @param encodedKey
     * @param content
     * @param charset
     * @return
     * @throws Exception
     */
    public static String rsaEncryptByPublic(String encodedKey, String content, String charset) throws Exception {
        if (StringUtils.isEmpty(encodedKey)) {
            throw new Exception("密钥为空");
        } else if (StringUtils.isEmpty(content)) {
            throw new Exception("待加密数据为空");
        } else {
            PublicKey key = null;
            try {
                key = getPublicKeyFromX509("RSA", encodedKey);
            } catch (Exception var5) {
                throw new Exception("加密异常", var5);
            }
            return encrypt(key, content, charset);
        }
    }

    /**
     * RSA 加密
     *
     * @param encodedKey
     * @param content
     * @param charset
     * @return
     * @throws Exception
     */
    private static String encrypt(Key encodedKey, String content, String charset) throws Exception {
        if (Objects.isNull(encodedKey)) {
            throw new Exception("加密密钥为空");
        }
        if (StringUtils.isEmpty(content)) {
            throw new Exception("待加密数据为空");
        }
        try {
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            Throwable var4 = null;
            try {
                Cipher cipher = Cipher.getInstance("RSA");
                cipher.init(1, encodedKey);
                byte[] data = null;
                if (StringUtils.isBlank(charset)) {
                    data = content.getBytes();
                } else {
                    data = content.getBytes(charset);
                }
                int inputLen = data.length;
                int offSet = 0;

                for (int i = 0; inputLen - offSet > 0; offSet = i * 117) {
                    byte[] cache;
                    if (inputLen - offSet > 117) {
                        cache = cipher.doFinal(data, offSet, 117);
                    } else {
                        cache = cipher.doFinal(data, offSet, inputLen - offSet);
                    }
                    out.write(cache, 0, cache.length);
                    ++i;
                }
                return Base64.getEncoder().encodeToString(out.toByteArray());
            } catch (Throwable var21) {
                var4 = var21;
                throw var21;
            } finally {
                if (var4 != null) {
                    try {
                        out.close();
                    } catch (Throwable var20) {
                        var4.addSuppressed(var20);
                    }
                } else {
                    out.close();
                }
            }
        } catch (Exception var23) {
            throw new Exception("加密异常", var23);
        }
    }

    /**
     * AES加密
     *
     * @param key
     * @param data
     * @return
     * @throws GeneralSecurityException
     * @throws DecoderException
     */
    public static String aesEncrypt(String key, String data) throws GeneralSecurityException, DecoderException {
        if (key != null && data != null) {
            byte[] raw = Hex.decodeHex(key.toCharArray());
            SecretKeySpec skySpec = new SecretKeySpec(raw, "AES");
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            cipher.init(Cipher.ENCRYPT_MODE, skySpec, new IvParameterSpec(new byte[16]));
            return Hex.encodeHexString(cipher.doFinal(data.getBytes(StandardCharsets.UTF_8)));
        } else {
            return null;
        }
    }

    /**
     * AES解密
     *
     * @param key
     * @param encrypted
     * @return
     * @throws GeneralSecurityException
     * @throws DecoderException
     */
    public static String aesDecrypt(String key, String encrypted) throws GeneralSecurityException, DecoderException {
        if (key != null && encrypted != null) {
            byte[] raw = Hex.decodeHex(key.toCharArray());
            SecretKeySpec sKeySpec = new SecretKeySpec(raw, "AES");
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            cipher.init(2, sKeySpec, new IvParameterSpec(new byte[16]));
            byte[] original = cipher.doFinal(Hex.decodeHex(encrypted.toCharArray()));
            return new String(original, StandardCharsets.UTF_8);
        } else {
            return null;
        }
    }

    public static PublicKey getPublicKeyFromX509(String algorithm, String encodedKey) throws Exception {
        KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
        byte[] key = Base64.getDecoder().decode(encodedKey);
        return keyFactory.generatePublic(new X509EncodedKeySpec(key));
    }

    public static PrivateKey getPrivateKeyFromPKCS8(String algorithm, String encodedKey) throws Exception {
        KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
        byte[] key = Base64.getDecoder().decode(encodedKey);
        return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(key));
    }

    /**
     * 对参数排序
     *
     * @param sortedParams
     * @param salt
     * @return
     */
    public static String sortParams(Map<String, Object> sortedParams, String salt) {
        List<String> keys = new ArrayList(sortedParams.keySet());
        Collections.sort(keys);
        StringBuilder content = new StringBuilder();
        int index = 0;
        int i = 0;
        for (int size = keys.size(); i < size; ++i) {
            String key = (String) keys.get(i);
            Object value = sortedParams.get(key);
            if (!Objects.isNull(value) && (!(value instanceof List) || !((List) value).isEmpty()) && (!(value instanceof String) || !StringUtils.isBlank((String) value))) {
                content.append((index == 0 ? "" : "&") + key + "=" + value);
                ++index;
            }
        }
        if (StringUtils.isNotBlank(salt)) {
            content.append("&key=").append(salt);
        }
        return content.toString();
    }

    @Data
    @Builder
    static class RequestProtocol {
        private String appId;
        private Long timestamp;
        private String randomKey;
        private String sign;
        private String bizContent;

    }

    //AES key生成类
    //生成随机AES秘钥aesEncryptKey = RandomStringUtils.randomNumeric(32);
    public static class RandomStringUtils {
        private static final Random RANDOM = new Random();

        private RandomStringUtils() {
        }

        public static String random(int count) {
            return random(count, false, false);
        }

        public static String randomAscii(int count) {
            return random(count, 32, 127, false, false);
        }

        public static String randomAlphabetic(int count) {
            return random(count, true, false);
        }

        public static String randomAlphanumeric(int count) {
            return random(count, true, true);
        }

        public static String randomNumeric(int count) {
            return random(count, false, true);
        }

        public static String random(int count, boolean letters, boolean numbers) {
            return random(count, 0, 0, letters, numbers);
        }

        public static String random(int count, int start, int end, boolean letters, boolean numbers) {
            return random(count, start, end, letters, numbers, (char[]) null, RANDOM);
        }

        public static String random(int count, int start, int end, boolean letters, boolean numbers, char... chars) {
            return random(count, start, end, letters, numbers, chars, RANDOM);
        }

        public static String random(int count, int start, int end, boolean letters, boolean numbers, char[] chars, Random random) {
            if (count == 0) {
                return "";
            } else if (count < 0) {
                throw new IllegalArgumentException("Requested random string length " + count + " is less than 0.");
            } else if (chars != null && chars.length == 0) {
                throw new IllegalArgumentException("The chars array must not be empty");
            } else {
                if (start == 0 && end == 0) {
                    if (chars != null) {
                        end = chars.length;
                    } else if (!letters && !numbers) {
                        end = 2147483647;
                    } else {
                        end = 123;
                        start = 32;
                    }
                } else if (end <= start) {
                    throw new IllegalArgumentException("Parameter end (" + end + ") must be greater than start (" + start + ")");
                }

                char[] buffer = new char[count];
                int gap = end - start;

                while (true) {
                    while (true) {
                        while (count-- != 0) {
                            char ch;
                            if (chars == null) {
                                ch = (char) (random.nextInt(gap) + start);
                            } else {
                                ch = chars[random.nextInt(gap) + start];
                            }

                            if (letters && Character.isLetter(ch) || numbers && Character.isDigit(ch) || !letters && !numbers) {
                                if (ch >= '\udc00' && ch <= '\udfff') {
                                    if (count == 0) {
                                        ++count;
                                    } else {
                                        buffer[count] = ch;
                                        --count;
                                        buffer[count] = (char) ('\ud800' + random.nextInt(128));
                                    }
                                } else if (ch >= '\ud800' && ch <= '\udb7f') {
                                    if (count == 0) {
                                        ++count;
                                    } else {
                                        buffer[count] = (char) ('\udc00' + random.nextInt(128));
                                        --count;
                                        buffer[count] = ch;
                                    }
                                } else if (ch >= '\udb80' && ch <= '\udbff') {
                                    ++count;
                                } else {
                                    buffer[count] = ch;
                                }
                            } else {
                                ++count;
                            }
                        }

                        return new String(buffer);
                    }
                }
            }
        }

        public static String random(int count, String chars) {
            return chars == null ? random(count, 0, 0, false, false, (char[]) null, RANDOM) : random(count, chars.toCharArray());
        }

        public static String random(int count, char... chars) {
            return chars == null ? random(count, 0, 0, false, false, (char[]) null, RANDOM) : random(count, 0, chars.length, false, false, chars, RANDOM);
        }

    }

}


